//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include <bulverde.h>
#include <INTCBits.h>
#include <DMACBits.h>
#include "drvpub.h"
#include "dma.h"

#define IRQ_DMAC 25
#define DMA_CHANNELS 32

static CDMA g_dma;
// Types for internal usage
typedef struct {
    long  Used;
    void * Handler;
    void * Device;
} DmaChannel;

DmaChannel g_dmaChannels[DMA_CHANNELS];

const long DmaPriority[] = {
        0, 0, 0, 0, 1, 1, 1, 1,
        2, 2, 2, 2, 3, 3, 3, 3,
        0, 0, 0, 0, 1, 1, 1, 1,
        2, 2, 2, 2, 3, 3, 3, 3
};

Boolean GpioInit();

// Select a channel for the special priority
static long DmaSelectChannel(long priority)
{
    long i;

    if (priority < 0 || priority >= 4) return -1;

    for (i = 0; i < DMA_CHANNELS; i++) {
        if (DmaPriority[i] == priority && !g_dmaChannels[i].Used) {
            // Find an unsed channel!
            g_dmaChannels[i].Used = 1;
            return i;
        }
    }

    // If there is not any free channel in this priority,
    // then select in lower priority.
    return DmaSelectChannel(++priority);
}

EXTERN_C int Drv_DmaInstall(uint_t priority, void *Device, void * Handler)
{
    int Channel;

    Channel = DmaSelectChannel(priority);
    if (Channel == -1) return -1;
    Drv_Assert(Channel >= 0 && Channel < DMA_CHANNELS);

    g_dmaChannels[Channel].Handler = Handler;
    g_dmaChannels[Channel].Device = Device;
    return Channel;
}

static void DispatchDmaIrq(long Channel)
{
    typedef Boolean (*IsrHandler)(void *, Boolean *);
    if (g_dmaChannels[Channel].Used && g_dmaChannels[Channel].Handler) {
        ((IsrHandler)g_dmaChannels[Channel].Handler)(g_dmaChannels[Channel].Device, NULL);
//        DCSR |= DCSR_ENDINTR;
    }
}

// This algorithm will improve the speed of DMA irq dispatch
static void DispatchDmaIrqTerm(long DmaIntTerm, long DmaChannelBase)
{
    switch(DmaIntTerm)  {
    case 0x0:
        break;
    case 0x1:
        DispatchDmaIrq(DmaChannelBase + 0);
        break;
    case 0x2:
        DispatchDmaIrq(DmaChannelBase + 1);
        break;
    case 0x3:
        DispatchDmaIrq(DmaChannelBase + 0);
        DispatchDmaIrq(DmaChannelBase + 1);
        break;
    case 0x4:
        DispatchDmaIrq(DmaChannelBase + 2);
        break;
    case 0x5:
        DispatchDmaIrq(DmaChannelBase + 0);
        DispatchDmaIrq(DmaChannelBase + 2);
        break;
    case 0x6:
        DispatchDmaIrq(DmaChannelBase + 1);
        DispatchDmaIrq(DmaChannelBase + 2);
        break;
    case 0x7:
        DispatchDmaIrq(DmaChannelBase + 0);
        DispatchDmaIrq(DmaChannelBase + 1);
        DispatchDmaIrq(DmaChannelBase + 2);
        break;
    case 0x8:
        DispatchDmaIrq(DmaChannelBase + 3);
        break;
    case 0x9:
        DispatchDmaIrq(DmaChannelBase + 0);
        DispatchDmaIrq(DmaChannelBase + 3);
        break;
    case 0xA:
        DispatchDmaIrq(DmaChannelBase + 1);
        DispatchDmaIrq(DmaChannelBase + 3);
        break;
    case 0xB:
        DispatchDmaIrq(DmaChannelBase + 0);
        DispatchDmaIrq(DmaChannelBase + 1);
        DispatchDmaIrq(DmaChannelBase + 3);
        break;
    case 0xC:
        DispatchDmaIrq(DmaChannelBase + 2);
        DispatchDmaIrq(DmaChannelBase + 3);
        break;
    case 0xD:
        DispatchDmaIrq(DmaChannelBase + 0);
        DispatchDmaIrq(DmaChannelBase + 2);
        DispatchDmaIrq(DmaChannelBase + 3);
        break;
    case 0xE:
        DispatchDmaIrq(DmaChannelBase + 1);
        DispatchDmaIrq(DmaChannelBase + 2);
        DispatchDmaIrq(DmaChannelBase + 3);
        break;
    case 0xF:
        DispatchDmaIrq(DmaChannelBase + 0);
        DispatchDmaIrq(DmaChannelBase + 1);
        DispatchDmaIrq(DmaChannelBase + 2);
        DispatchDmaIrq(DmaChannelBase + 3);
        break;
    }
}

// Interrupt routine for DMA
//static Boolean DmaIsr(void *pThis, Boolean * pWakeup)
void CDECL DmaIsr(irq_t irq, void * pvDevice,InterruptContext * pContext)
{
    long dint;
    long i;

//    INTC_DMAC_INT_DIS(ICMR);
    dint = DINT;
    for (i = 0; i < DMA_CHANNELS; i += 4) {
        if (dint == 0) break; // No interrupt
        DispatchDmaIrqTerm(dint & 0xF, i);
        dint >>= 4;
    }
//    INTC_DMAC_INT_EN(ICMR);
}

// Add the DMA device to system
Boolean DmaInit()
{
    long i;

    DzRegisterIsr(IRQ_DMAC, IPL10, (isr_t)DmaIsr, NULL);


    for (i = 0; i < DMA_CHANNELS; i++) {
        g_dmaChannels[i].Used = 0;
        g_dmaChannels[i].Handler = NULL;
        g_dmaChannels[i].Device = NULL;
    }

    return TRUE;
}

/////////////////////////////////////////////////////////////////////
#if 0
EXTERN_C UInt32 save_flags_cli();
EXTERN_C void restore_flags(UInt32);

EXTERN_C void SSP_Initial(void);

static unsigned long sLock = 0;
EXTERN_C int SSP_TryLock()
{
    unsigned long iflags;
    unsigned long flags = 0;
    int count = 0;
//    assert(sLock != 2);

retry:
    iflags = save_flags_cli();
    flags = ICMR & (0x1  << 10);
//    if (flags) BTTHR = 'E';
//    else {
//        BTTHR = 'D';
//    }

    if (sLock == 0) {
        sLock = 2;
        ICMR &= ~(0x1  << 10);      // irq = 10, disable interrupt
    }

    restore_flags(iflags);
    if (sLock != 2) {
//TMP        DzSleep(100 * 10000);
        DzDelay(100);
        count ++;
        if (count < 20) {
            goto retry;
        }
        else {
            count = 0;
            SSP_Initial();
            sLock = 0;
        }

    }
    return flags;

}

EXTERN_C int SSP_Lock()
{
    sLock = 1;
    return 0;
}

EXTERN_C int SSP_Unlock()
{
    sLock = 0;
    return 0;
}

EXTERN_C int _TSC_RegRead(unsigned long page, unsigned long addr, unsigned short* data, unsigned long dwNum);
EXTERN_C int _TSC_RegWrite(unsigned long page, unsigned long addr, unsigned short* data, unsigned long dwNum);


EXTERN_C int TSC_RegRW(int i, unsigned long page, unsigned long addr, unsigned short* data, unsigned long dwNum)
{
    int ret;

    unsigned long flags = SSP_TryLock();

    if (i == 0) //read
        ret = _TSC_RegRead(page, addr, data, dwNum);
    else
        ret = _TSC_RegWrite(page, addr, data, dwNum);

//TMP    DzSleep(100 * 10000);
    DzDelay(100 * 1000);
    SSP_Unlock();

    if (flags) {
        ICMR |= (0x1  << 10);       // irq = 10, enable interrupt
    }
    return ret;
}

EXTERN_C int TSC_RegRead(unsigned long page, unsigned long addr, unsigned short* data, unsigned long dwNum)
{
    return TSC_RegRW(0, page, addr, data, dwNum);
}

EXTERN_C int TSC_RegWrite(unsigned long page, unsigned long addr, unsigned short* data, unsigned long dwNum)
{
    return TSC_RegRW(1, page, addr, data, dwNum);
}
#endif

EXTERN IDeviceDriver * CDECL CreateDMA(uint_t uDeviceNo, void *pvParameter)
{
    if(!DmaInit())return NULL;
    kprintf("Add public driver library successfully!\n");
    return &g_dma;
}

ECode CDMA::Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode CDMA::Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode CDMA::Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}
